#ifndef __MATRIX_H__
#define __MATRIX_H__

#define USE_NLOHMANN_JSON 1

#include "Vector3.hpp"

#if USE_NLOHMANN_JSON
#include "nlohmann/json.hpp"
#endif


namespace PocketSurvival::Engine
{
    struct Matrix
    {
        union
        {
            float buff[16];
            struct
            {
                float M11, M12, M13, M14;
                float M21, M22, M23, M24;
                float M31, M32, M33, M34;
                float M41, M42, M43, M44;
            };
        };

        Matrix() = default;

        constexpr Matrix(float m11, float m12, float m13, float m14, 
                         float m21, float m22, float m23, float m24, 
                         float m31, float m32, float m33, float m34, 
                         float m41, float m42, float m43, float m44) noexcept;

        inline void setRight(const Vector3 &right);
        inline Vector3 getRight() const;

        inline void setUp(const Vector3 &up);
        inline Vector3 getUp() const;

        inline void setForward(const Vector3 &forward);
        inline Vector3 getForward() const;

        inline void setTranslation(const Vector3 &translation);
        inline Vector3 getTranslation() const;

        inline void setOrientationMatrix(const Matrix &matrix);
        inline Matrix getOrientationMatrix() const;

        float Determinant();

        
        static void CreateFromAxisAngle(const Vector3 &axis, float angle, Matrix &outMatrix);
        static void CreateLookAt(const Vector3 &position, const Vector3 &target, const Vector3 &up, Matrix &outMatrix);
        static void CreateRotationX(float radians, Matrix &outMatrix);
        static void CreateRotationY(float radians, Matrix &outMatrix);
        static void CreateRotationZ(float radians, Matrix &outMatrix);

        static inline void IdentityInit(Matrix &outMatrix)
        {
            outMatrix.M11 = 1.0f; outMatrix.M12 = 0.0f; outMatrix.M13 = 0.0f; outMatrix.M14 = 0.0f;
            outMatrix.M21 = 0.0f; outMatrix.M22 = 1.0f; outMatrix.M23 = 0.0f; outMatrix.M24 = 0.0f;
            outMatrix.M31 = 0.0f; outMatrix.M32 = 0.0f; outMatrix.M33 = 1.0f; outMatrix.M34 = 0.0f;
            outMatrix.M41 = 0.0f; outMatrix.M42 = 0.0f; outMatrix.M43 = 0.0f; outMatrix.M44 = 1.0f;
        }
        static inline Matrix CreateScale(float x, float y, float z)
		{
			return Matrix { x, 0.0f, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 0.0f, z, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
		}
		static inline Matrix CreateScale(const Vector3 &scale)
		{
			return Matrix { scale.posX, 0.0f, 0.0f, 0.0f, 0.0f, scale.posY, 0.0f, 0.0f, 0.0f, 0.0f, scale.posZ, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
		}
		static inline Matrix CreateTranslation(float x, float y, float z)
		{
			return Matrix { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, x, y, z, 1.0f };
		}
		static inline Matrix CreateTranslation(const Vector3 &position)
		{
			return Matrix { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, position.posX, position.posY, position.posZ, 1.0f };
		}
        friend constexpr inline Matrix operator*(const Matrix &left, const Matrix &right)
        {
            return Matrix {
                    left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31 + left.M14 * right.M41,
                    left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32 + left.M14 * right.M42,
                    left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33 + left.M14 * right.M43,
                    left.M11 * right.M14 + left.M12 * right.M24 + left.M13 * right.M34 + left.M14 * right.M44,
                    left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31 + left.M24 * right.M41,
                    left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32 + left.M24 * right.M42,
                    left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33 + left.M24 * right.M43,
                    left.M21 * right.M14 + left.M22 * right.M24 + left.M23 * right.M34 + left.M24 * right.M44,
                    left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31 + left.M34 * right.M41,
                    left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32 + left.M34 * right.M42,
                    left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 + left.M34 * right.M43,
                    left.M31 * right.M14 + left.M32 * right.M24 + left.M33 * right.M34 + left.M34 * right.M44,
                    left.M41 * right.M11 + left.M42 * right.M21 + left.M43 * right.M31 + left.M44 * right.M41,
                    left.M41 * right.M12 + left.M42 * right.M22 + left.M43 * right.M32 + left.M44 * right.M42,
                    left.M41 * right.M13 + left.M42 * right.M23 + left.M43 * right.M33 + left.M44 * right.M43,
                    left.M41 * right.M14 + left.M42 * right.M24 + left.M43 * right.M34 + left.M44 * right.M44
            };
        }
        friend constexpr inline bool operator==(const Matrix &left, const Matrix &right)
        {
            return  left.M11 == right.M11 && left.M21 == right.M21 && left.M31 == right.M31 && left.M41 == right.M41 &&
                    left.M12 == right.M12 && left.M22 == right.M22 && left.M32 == right.M32 && left.M42 == right.M42 &&
                    left.M13 == right.M13 && left.M23 == right.M23 && left.M33 == right.M33 && left.M43 == right.M43 &&
                    left.M14 == right.M14 && left.M24 == right.M24 && left.M34 == right.M34 && left.M44 == right.M44;
        }
        friend constexpr inline bool operator!=(const Matrix &left, const Matrix &right)
        {
            return !(left == right);
        }

#if USE_NLOHMANN_JSON
        friend inline void to_json(nlohmann::json &json, const Vector3 &vec);
        friend inline void from_json(const nlohmann::json &json, Vector3 &vec);
#endif


    };

    constexpr inline Matrix::Matrix(float m11, float m12, float m13, float m14, 
                                    float m21, float m22, float m23, float m24, 
                                    float m31, float m32, float m33, float m34, 
                                    float m41, float m42, float m43, float m44) noexcept :
                                        M11(m11), M12(m12), M13(m13), M14(m14),
                                        M21(m21), M22(m22), M23(m23), M24(m24),
                                        M31(m31), M32(m32), M33(m33), M34(m34),
                                        M41(m41), M42(m42), M43(m43), M44(m44)
                                    {}

    inline void Matrix::setRight(const Vector3 &right)
    {
        M11 = right.posX;
        M12 = right.posY;
        M13 = right.posZ;
    }
    inline Vector3 Matrix::getRight() const
    {
        return Vector3 { M11, M12, M13 };
    }
    inline void Matrix::setUp(const Vector3 &up)
    {
        M21 = up.posX;
        M22 = up.posY;
        M23 = up.posZ;
    }
    inline Vector3 Matrix::getUp() const
    {
        return Vector3 { M21, M22, M23 };
    }
    inline void Matrix::setForward(const Vector3 &forward)
    {
        M31 = -forward.posX;
        M32 = -forward.posY;
        M33 = -forward.posZ;
    }
    inline Vector3 Matrix::getForward() const
    {
        return Vector3 { -M31, -M32, -M33 };
    }
    inline void Matrix::setTranslation(const Vector3 &translation)
    {
        M41 = translation.posX;
        M42 = translation.posY;
        M43 = translation.posZ;
    }
    inline Vector3 Matrix::getTranslation() const
    {
        return Vector3 { M41, M42, M43 };
    }
    inline void Matrix::setOrientationMatrix(const Matrix &matrix)
    {
		M11 = matrix.M11;
		M12 = matrix.M12;
		M13 = matrix.M13;
		M21 = matrix.M21;
		M22 = matrix.M22;
		M23 = matrix.M23;
		M31 = matrix.M31;
		M32 = matrix.M32;
		M33 = matrix.M33;
    }
    inline Matrix Matrix::getOrientationMatrix() const
    {
        return Matrix { M11, M12, M13, 0.0f, M21, M22, M23, 0.0f, M31, M32, M33, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };
    }

#if USE_NLOHMANN_JSON

    inline void to_json(nlohmann::json &json, const Matrix &matrix)
    {
        for(int32_t index = 0; index < 16; ++index)
        {
            json.push_back(matrix.buff[index]);
        }
    }
    inline void from_json(const nlohmann::json &json, Matrix &matrix)
    {
        for(int32_t index = 0; index < 16; ++index)
        {
            matrix.buff[index] = json[index].get<float>();
        }
    }

#endif


    namespace FastMatrix
    {
        constexpr inline Matrix Zero{   0.0f, 0.0f, 0.0f, 0.0f, 
                                        0.0f, 0.0f, 0.0f, 0.0f, 
                                        0.0f, 0.0f, 0.0f, 0.0f, 
                                        0.0f, 0.0f, 0.0f, 0.0f};

        constexpr inline Matrix Identity{   1.0f, 0.0f, 0.0f, 0.0f,
                                            0.0f, 1.0f, 0.0f, 0.0f,
                                            0.0f, 0.0f, 1.0f, 0.0f,
                                            0.0f, 0.0f, 0.0f, 1.0f};
    }

} // namespace PocketSurvival

#endif
